/**
 * React Starter Kit (https://www.reactstarterkit.com/)
 *
 * Copyright © 2014-present Kriasoft, LLC. All rights reserved.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE.txt file in the root directory of this source tree.
 */

require('colors');
require('./server/middlewares/mongoose_log'); // 打印 mongodb 查询日志
require('./server/models');
require('./server/common/init');

import path from 'path';
import express from 'express';
import cookieParser from 'cookie-parser';
import connectMongodb from 'connect-mongo';
import bodyParser from 'body-parser';
import expressJwt from 'express-jwt';
import expressGraphQL from 'express-graphql';
import jwt from 'jsonwebtoken';
import React from 'react';
import ReactDOM from 'react-dom/server';
import UniversalRouter from 'universal-router';
import PrettyError from 'pretty-error';
import App from './components/App';
import Html from './components/Html';
import { ErrorPageWithoutStyle } from './routes/error/ErrorPage';
import errorPageStyle from './routes/error/ErrorPage.css';
import routes from './routes';
import assets from './assets.json'; // eslint-disable-line import/no-unresolved
import { host, debug, port, auth, redisInfo, mongodbUrl, mini_assets, apiPrefix } from './config';

import serverConfig from './server/config';
if (!debug && serverConfig.oneapm_key) {
  require('oneapm');
}

import Loader from 'loader';
// var LoaderConnect = require('loader-connect');
import session from 'express-session';

import pageRouter from './server/pageRouter';
import dataRouter from './server/dataRouter';
import { authUser, blockUser } from './server/middlewares/auth';
import proxyMiddleware from './server/middlewares/proxy';
import _ from 'lodash';
// import csrf from 'csurf';
import compress from 'compression';
import busboy from 'connect-busboy';
import errorhandler from 'errorhandler';
import cors from 'cors';
import { requestLog } from './server/middlewares/request_log';
import renderMiddleware from './server/middlewares/render';
import logger from './server/common/logger';
import helmet from 'helmet';
import bytes from 'bytes';
import multiline from 'multiline';
import markdown from './server/common/markdown';

import { zoneRequired, zonesRequired } from './server/middlewares/base_data';
import { errorJson } from './server/common/response';

const MongoStore = new connectMongodb(session);
// assets
let serverAssets = {};

if (mini_assets) {
  try {
    serverAssets = require('./server/assets.json');
  } catch (e) {
    logger.error('You must execute `make build` before start app when mini_assets is true.');
    throw e;
  }
}

const app = express();

// configuration in all env
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'html');
app.engine('html', require('ejs-mate'));
app.locals._layoutFile = 'layout.html';
app.enable('trust proxy');
// Request logger。请求时间
app.use(requestLog);
if (serverConfig.debug) {
  // 渲染时间
  app.use(renderMiddleware);
}

const corsOptions = {
  origin: true,
  methods: ['get', 'post'],
  credentials: true,
  maxAge: 3600,
};

app.use(cors(corsOptions));

//
// Tell any CSS tooling (such as Material UI) to use all vendor prefixes if the
// user agent is not known.
// -----------------------------------------------------------------------------
global.navigator = global.navigator || {};
global.navigator.userAgent = global.navigator.userAgent || 'all';

//
// Register Node.js middleware
// -----------------------------------------------------------------------------
app.use(express.static(path.join(__dirname, 'public')));
app.use(cookieParser(serverConfig.session_secret));
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

//
// for server.
// -----------------------------------------------------------------------------
// 静态文件目录
app.use('/public', express.static(path.join(__dirname, 'public')));
app.use('/agent', proxyMiddleware);
// 通用的中间件
app.use(require('response-time')());
app.use(helmet.frameguard('sameorigin'));
// app.use(bodyParser.json({limit: '1mb'}));
// app.use(bodyParser.urlencoded({ extended: true, limit: '1mb' }));
// app.use(csrf({ cookie: true }));
app.use(require('method-override')());
app.use(compress());
app.use(require('cookie-parser')(serverConfig.session_secret));
app.use(session({
  secret: serverConfig.session_secret,
  store: new MongoStore({
    url: mongodbUrl,
  }),
  resave: true,
  saveUninitialized: true,
}));

serverConfig.mini_assets = mini_assets;

_.extend(app.locals, {
  config: serverConfig,
  Loader,
  assets: serverAssets,
  apiPrefix,
  markdown,
  multiline,
  _,
  staticFile(filePath) {
    if (filePath.indexOf('http') === 0 || filePath.indexOf('//') === 0) {
      return filePath;
    }
    return serverConfig.site_static_host + filePath;
  },
  proxy(url) {
    return url;
    // 当 google 和 github 封锁严重时，则需要通过服务器代理访问它们的静态资源
    // return '/agent?url=' + encodeURIComponent(url);
  },
});

app.use((req, res, next) => {
  res.locals.appId = serverConfig.appInfo.id;
  app.locals.appId = serverConfig.appInfo.id;
  next();
});

// custom middleware
app.use(authUser);
app.use(blockUser());
app.use(zoneRequired);
app.use(zonesRequired);

app.use((req, res, next) => {
  res.locals.csrf = req.csrfToken ? req.csrfToken() : '';
  next();
});

app.use(busboy({
  limits: {
    fileSize: bytes(serverConfig.file_limit),
  },
}));

app.use((req, res, next) => {
  logger.debug(('query').red, JSON.stringify(req.query));
  logger.debug(('body').red, JSON.stringify(req.body));
  next();
});

//
// Authentication
// -----------------------------------------------------------------------------
app.use(expressJwt({
  secret: auth.jwt.secret,
  credentialsRequired: false,
  getToken: req => req.cookies.id_token,
}));
// app.use(passport.initialize());

if (__DEV__) {
  app.enable('trust proxy');
}
// app.get('/login/facebook',
//   passport.authenticate('facebook', { scope: ['email', 'user_location'], session: false }),
// );
// app.get('/login/facebook/return',
//   passport.authenticate('facebook', { failureRedirect: '/login', session: false }),
//   (req, res) => {
//     const expiresIn = 60 * 60 * 24 * 180; // 180 days
//     const token = jwt.sign(req.user, auth.jwt.secret, { expiresIn });
//     res.cookie('id_token', token, { maxAge: 1000 * expiresIn, httpOnly: true });
//     res.redirect('/');
//   },
// );

//
// Register API middleware
// -----------------------------------------------------------------------------
// app.use('/graphql', expressGraphQL(req => ({
//   schema,
//   graphiql: __DEV__,
//   rootValue: { request: req },
//   pretty: __DEV__,
// })));

//
// Register server-side rendering middleware
// -----------------------------------------------------------------------------
app.get(apiPrefix.react, async (req, res, next) => {
  try {
    const css = new Set();

    // Global (context) variables that can be easily accessed from any React component
    // https://facebook.github.io/react/docs/context.html
    const context = {
      // Enables critical path CSS rendering
      // https://github.com/kriasoft/isomorphic-style-loader
      insertCss: (...styles) => {
        // eslint-disable-next-line no-underscore-dangle
        styles.forEach(style => css.add(style._getCss()));
      },
    };

    const route = await UniversalRouter.resolve(routes, {
      path: req.path,
      query: req.query,
    });

    if (route.redirect) {
      res.redirect(route.status || 302, route.redirect);
      return;
    }

    const data = { ...route };
    data.csrf = res.locals.csrf || '';
    data.children = ReactDOM.renderToString(<App context={context}>{route.component}</App>);
    console.log(data.children);
    data.styles = [
      { id: 'css', cssText: [...css].join('') },
    ];
    data.scripts = [
      assets.vendor.js,
      assets.client.js,
    ];
    if (assets[route.chunk]) {
      data.scripts.push(assets[route.chunk].js);
    }

    const html = ReactDOM.renderToStaticMarkup(<Html {...data} />);
    res.status(route.status || 200);
    res.send(`<!doctype html>${html}`);
  } catch (err) {
    next(err);
  }
});

// routes

app.use(apiPrefix.page, pageRouter);
app.use(apiPrefix.data, dataRouter);

//
// Error handling
// -----------------------------------------------------------------------------
const pe = new PrettyError();
pe.skipNodeFiles();
pe.skipPackage('express');

app.use((err, req, res, next) => { // eslint-disable-line no-unused-vars
  console.log(pe.render(err)); // eslint-disable-line no-console
  logger.error(err);

  if (typeof (err) !== 'object') {
    err = new Error(err);
  }

  if (req.xhr) {
    const statusCode = res.statusCode || 500;

    res.status(statusCode).json({ err });
  } else {
    const html = ReactDOM.renderToStaticMarkup(
      <Html
        title="Internal Server Error"
        description={err.message}
        style={errorPageStyle._getCss()} // eslint-disable-line no-underscore-dangle
      >
        {ReactDOM.renderToString(<ErrorPageWithoutStyle error={err} />)}
      </Html>,
    );
    res.status(err.status || 500);
    res.send(`<!doctype html>${html}`);
  }
});
//
// Launch the server
// -----------------------------------------------------------------------------
/* eslint-disable no-console */
// models.sync().catch(err => console.error(err.stack)).then(() => {
app.listen(port, () => {
  console.log(`The server is running at http://localhost:${port}/`);
});
// });
/* eslint-enable no-console */
